package configure

import (
	"gitee.com/kristas/booting-go/framework/common/util/reflectx"
	"gitee.com/kristas/booting-go/framework/core/statement"
	"gopkg.in/yaml.v3"
	"reflect"
	"strings"
)

func Get(valuePath string) interface{} {
	result := walkConfig(valuePath, _config)
	return result
}

func BindPath(valuePath string, o interface{}) error {
	result := Get(valuePath)
	return copyValue(result, o)
}

func BindConfiguration(o statement.AutoConfigure) error {
	if state, ok := o.(statement.Statement); ok {
		statement.Compile(state)
	}
	result := Get(o.Prefix())
	return copyValue(result, o)
}

func copyValue(source, target interface{}) error {
	bytes, err := yaml.Marshal(source)
	if err != nil {
		return err
	}
	err = yaml.Unmarshal(bytes, target)
	if err != nil {
		return err
	}
	return nil
}

func Bind(structure interface{}) error {
	return reflectx.ForEachField(structure, true, func(field reflect.StructField, value reflect.Value) error {
		if tag, ok := field.Tag.Lookup("value"); ok {
			return inject(field.Type, value, func(cfg reflect.Value) error {
				return BindPath(tag, cfg.Interface())
			})
		} else if field.Type.Implements(reflect.TypeOf(new(statement.AutoConfigure)).Elem()) {
			return inject(field.Type, value, func(cfg reflect.Value) error {
				return BindConfiguration(cfg.Interface().(statement.AutoConfigure))
			})
		}
		return nil
	})
}

func inject(typ reflect.Type, value reflect.Value, injector func(value reflect.Value) error) error {
	var sourceType = typ
	var isPtr = sourceType.Kind() == reflect.Ptr
	if isPtr {
		sourceType = sourceType.Elem()
	}
	cfg := reflect.New(sourceType)
	err := injector(cfg)
	if err != nil {
		return err
	}
	if isPtr {
		value.Set(cfg)
	} else {
		value.Set(cfg.Elem())
	}
	return nil
}

func walkConfig(s string, m map[string]interface{}) interface{} {
	if !strings.Contains(s, ".") {
		return m[s]
	}
	arr := strings.SplitN(s, ".", 2)
	if m[arr[0]] == nil {
		panic("config parse error: " + s)
	}
	innerMap := m[arr[0]].(map[string]interface{})
	return walkConfig(arr[1], innerMap)
}
